// tools/audit-seal.c
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <time.h>

#include <openssl/evp.h>
#include <openssl/pkcs12.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>

#include "../../lib/gm_crypto.h"

#define SALT_PATH "/etc/pki/audit/p12_salt"
#define P12_PATH "/etc/pki/audit/sm2.p12"
#define SM2_ID "1234567812345678"
#define SALT_BYTES 16
#define DERIVE_BYTES 32
#define DERIVE_ITERATIONS 200000

static void die(const char *msg)
{
    perror(msg);
    exit(1);
}
static void diee(const char *msg)
{
    fprintf(stderr, "%s\n", msg);
    exit(1);
}

static int sm3_file_hex(const char *path, char out_hex[65])
{
    const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
    if (!sm3)
        return -1;
    FILE *f = fopen(path, "rb");
    if (!f)
        return -1;
    EVP_MD_CTX *ctx = EVP_MD_CTX_new();
    if (!ctx)
    {
        fclose(f);
        return -1;
    }
    if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1)
    {
        EVP_MD_CTX_free(ctx);
        fclose(f);
        return -1;
    }

    unsigned char buf[4096];
    size_t n;
    while ((n = fread(buf, 1, sizeof(buf), f)) > 0)
    {
        if (EVP_DigestUpdate(ctx, buf, n) != 1)
        {
            EVP_MD_CTX_free(ctx);
            fclose(f);
            return -1;
        }
    }
    fclose(f);
    unsigned char md[32];
    unsigned int mdlen = 0;
    if (EVP_DigestFinal_ex(ctx, md, &mdlen) != 1 || mdlen != 32)
    {
        EVP_MD_CTX_free(ctx);
        return -1;
    }
    EVP_MD_CTX_free(ctx);
    static const char hex[] = "0123456789abcdef";
    for (int i = 0; i < 32; i++)
    {
        out_hex[i * 2] = hex[(md[i] >> 4) & 0xF];
        out_hex[i * 2 + 1] = hex[md[i] & 0xF];
    }
    out_hex[64] = 0;
    return 0;
}

static int build_line_index(const char *log_path, char **out_idx_path,
                            char index_sm3_hex[65], uint64_t *out_lines)
{
    FILE *fp = fopen(log_path, "rb");
    if (!fp)
        return -1;
    char *idx_path = NULL;
    if (asprintf(&idx_path, "%s.idx", log_path) < 0)
    {
        fclose(fp);
        return -1;
    }
    FILE *ix = fopen(idx_path, "w");
    if (!ix)
    {
        fclose(fp);
        free(idx_path);
        return -1;
    }

    // sm3 init
    const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
    if (!sm3)
    {
        fclose(fp);
        fclose(ix);
        free(idx_path);
        return -1;
    }

    // 头部内容
    fprintf(ix, "# audit-ins=dex v1\n");
    fprintf(ix, "path=%s\n", log_path);
    fprintf(ix, "hash=sm3\n");

    // 遍历行，记录lhash,chash,off
    unsigned char C[32] = {0};
    uint64_t lines = 0;
    uint64_t off = 0;

    char *line = NULL;
    size_t cap = 0;
    ssize_t n;
    while ((n = getline(&line, &cap, fp)) != -1)
    {
        // 计算lhash = SM3(line)
        unsigned char L[32];
        unsigned int lml = 0;
        EVP_MD_CTX *c1 = EVP_MD_CTX_new();
        if (!c1)
        {
            free(line);
            fclose(fp);
            fclose(fp);
            fclose(ix);
            free(idx_path);
            return -1;
        }
        if (EVP_DigestInit_ex(c1, sm3, NULL) != 1 ||
            EVP_DigestUpdate(c1, line, (size_t)n) != 1 ||
            EVP_DigestFinal_ex(c1, L, &lml) != 1 || lml != 32)
        {
            EVP_MD_CTX_free(c1);
            free(line);
            fclose(fp);
            fclose(ix);
            free(idx_path);
            return -1;
        }
        EVP_MD_CTX_free(c1);

        // 更新C=SM3(C||line)
        EVP_MD_CTX *c2 = EVP_MD_CTX_new();
        unsigned int cml = 0;
        if (!c2)
        {
            free(line);
            fclose(fp);
            fclose(ix);
            free(idx_path);
            return -1;
        }
        if (EVP_DigestInit_ex(c2, sm3, NULL) != 1 ||
            EVP_DigestUpdate(c2, C, sizeof(C)) != 1 ||
            EVP_DigestUpdate(c2, line, (size_t)n) != 1 ||
            EVP_DigestFinal_ex(c2, C, &cml) != 1 || cml != 32)
        {
            EVP_MD_CTX_free(c2);
            free(line);
            fclose(fp);
            fclose(ix);
            free(idx_path);
            return -1;
        }
        EVP_MD_CTX_free(c2);

        // 转hex
        static const char hex[] = "0123456789abcdef";
        char Lhex[65], Chex[65];
        for (int i = 0; i < 32; i++)
        {
            Lhex[i * 2] = hex[(L[i] >> 4) & 0xF];
            Lhex[i * 2 + 1] = hex[L[i] & 0xF];
        }
        Lhex[64] = 0;
        for (int i = 0; i < 32; i++)
        {
            Chex[i * 2] = hex[(C[i] >> 4) & 0xF];
            Chex[i * 2 + 1] = hex[C[i] & 0xF];
        }
        Chex[64] = 0;

        // 写索引行
        fprintf(ix, "i=%llu off=%llu lhash=%s chash=%s\n",
                (unsigned long long)(lines + 1),
                (unsigned long long)off,
                Lhex, Chex);

        off += (uint64_t)n;
        lines++;
    }
    free(line);
    fclose(fp);

    // 把 records 写在头部后面（简单写法：先写，文件末尾再补一行）
    fprintf(ix,"records=%llu\n",(unsigned long long)lines);
    fclose(ix);

    //计算 idx 文件的SM3
    if(sm3_file_hex(idx_path,index_sm3_hex)!=0)
    {
        free(idx_path);
        return -1;
    }

    if(out_lines) *out_lines=lines;
    *out_idx_path = idx_path;
    return 0;
}

static int read_salt_hexfile(const char *path, unsigned char *out, size_t outlen)
{
    FILE *f = fopen(path, "r");
    if (!f)
        return -1;
    char buf[128];
    if (!fgets(buf, sizeof(buf), f))
    {
        fclose(f);
        return -1;
    }
    fclose(f);
    size_t len = strlen(buf);
    while (len && (buf[len - 1] == '\n' || buf[len - 1] == '\r'))
        buf[--len] = '\0';
    if (len / 2 != outlen)
        return -1;
    for (size_t i = 0; i < outlen; i++)
    {
        unsigned int hi, lo;
        if (sscanf(&buf[i * 2], "%1x%1x", &hi, &lo) != 2)
            return -1;
        out[i] = (hi << 4) | lo;
    }
    return 0;
}

static void hexdump(const unsigned char *buf, size_t len, char *out, size_t outlen)
{
    static const char hex[] = "0123456789abcdef";
    if (outlen < len * 2 + 1)
        diee("hexdump out too small");
    for (size_t i = 0; i < len; i++)
    {
        out[i * 2] = hex[(buf[i] >> 4) & 0xF];
        out[i * 2 + 1] = hex[buf[i] & 0xF];
    }
    out[len * 2] = '\0';
}

static int derive_pass(const char *secret, const unsigned char *salt, int saltlen,
                       int iter, unsigned char *out, int outlen)
{
    const EVP_MD *md = EVP_get_digestbyname("sm3");
    if (!md)
        diee("[ERROR] OpenSSL 未提供 sm3，无法国密派生口令");
    if (!PKCS5_PBKDF2_HMAC(secret, strlen(secret), salt, saltlen, iter, md, outlen, out))
        return -1;
    return 0;
}

/* 计算链式 SM3：H0=0x00..00；逐行读（包含换行符原样参与计算） */
static int sm3_chain_file(const char *path, unsigned char out32[32], uint64_t *out_lines)
{
    const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
    if (!sm3)
        return -1;

    unsigned char H[32] = {0};
    uint64_t lines = 0;

    FILE *fp = fopen(path, "rb");
    if (!fp)
        return -1;

    char *line = NULL;
    size_t cap = 0;
    ssize_t n;

    while ((n = getline(&line, &cap, fp)) != -1)
    {
        EVP_MD_CTX *ctx = EVP_MD_CTX_new();
        if (!ctx)
        {
            fclose(fp);
            free(line);
            return -1;
        }
        if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1)
        {
            EVP_MD_CTX_free(ctx);
            fclose(fp);
            free(line);
            return -1;
        }
        if (EVP_DigestUpdate(ctx, H, sizeof(H)) != 1)
        {
            EVP_MD_CTX_free(ctx);
            fclose(fp);
            free(line);
            return -1;
        }
        if (EVP_DigestUpdate(ctx, line, (size_t)n) != 1)
        {
            EVP_MD_CTX_free(ctx);
            fclose(fp);
            free(line);
            return -1;
        }
        unsigned int mdlen = 0;
        if (EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32)
        {
            EVP_MD_CTX_free(ctx);
            fclose(fp);
            free(line);
            return -1;
        }
        EVP_MD_CTX_free(ctx);
        lines++;
    }
    free(line);
    fclose(fp);

    memcpy(out32, H, 32);
    if (out_lines)
        *out_lines = lines;
    return 0;
}

/* base64 工具 */
static char *b64_encode(const unsigned char *buf, size_t len)
{
    BIO *b64 = BIO_new(BIO_f_base64());
    BIO *mem = BIO_new(BIO_s_mem());
    if (!b64 || !mem)
    {
        if (b64)
            BIO_free(b64);
        if (mem)
            BIO_free(mem);
        return NULL;
    }
#ifdef BIO_FLAGS_BASE64_NO_NL
    BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
#endif
    BIO_push(b64, mem);
    if (BIO_write(b64, buf, len) <= 0)
    {
        BIO_free_all(b64);
        return NULL;
    }
    (void)BIO_flush(b64);
    BUF_MEM *bptr = NULL;
    BIO_get_mem_ptr(b64, &bptr);
    char *out = malloc(bptr->length + 1);
    memcpy(out, bptr->data, bptr->length);
    out[bptr->length] = '\0';
    BIO_free_all(b64);
    return out;
}

static int x509_sm3_fingerprint_hex(X509 *cert, char *out, size_t outlen)
{
    const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
    if (!sm3)
        return -1;
    unsigned char *der = NULL;
    int derlen = i2d_X509(cert, &der);
    if (derlen <= 0)
        return -1;
    unsigned char md[EVP_MAX_MD_SIZE];
    unsigned int mdlen = 0;
    EVP_MD_CTX *ctx = EVP_MD_CTX_new();
    if (!ctx)
    {
        OPENSSL_free(der);
        return -1;
    }
    if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1 ||
        EVP_DigestUpdate(ctx, der, derlen) != 1 ||
        EVP_DigestFinal_ex(ctx, md, &mdlen) != 1)
    {
        EVP_MD_CTX_free(ctx);
        OPENSSL_free(der);
        return -1;
    }
    EVP_MD_CTX_free(ctx);
    OPENSSL_free(der);
    hexdump(md, mdlen, out, outlen);
    return 0;
}

/* 载入 P12 -> pkey + cert（口令是十六进制文本） */
static int load_pkey_cert_from_p12(const char *p12_path, const char *pass_hex,
                                   EVP_PKEY **out_pkey, X509 **out_cert)
{
    FILE *f = fopen(p12_path, "rb");
    if (!f)
        return -1;
    PKCS12 *p12 = d2i_PKCS12_fp(f, NULL);
    fclose(f);
    if (!p12)
        return -1;
    EVP_PKEY *pkey = NULL;
    X509 *cert = NULL;
    if (!PKCS12_parse(p12, pass_hex, &pkey, &cert, NULL))
    {
        PKCS12_free(p12);
        return -1;
    }
    PKCS12_free(p12);
    *out_pkey = pkey;
    *out_cert = cert;
    return 0;
}

/* 构造规范化描述串 */
static char *build_canonical(const char *path, const char *chain_hex,
                             uint64_t lines, const char *key_fpr_hex,
                            const char *index_sm3_hex)
{
    char *buf = NULL;
    if (asprintf(&buf,
                 "version=1\n"
                 "hash=sm3\n"
                 "sign=sm2\n"
                 "sm2_id=%s\n"
                 "path=%s\n"
                 "records=%llu\n"
                 "chain=%s\n"
                 "key_fpr=%s\n"
                 "index_sm3=%s\n",
                 SM2_ID, path, (unsigned long long)lines, chain_hex, key_fpr_hex, index_sm3_hex) < 0)
        return NULL;
    return buf;
}

//---------------------------------
static void dump_hex_prefix(const char *tag, const unsigned char *buf, size_t len)
{
    size_t n = len > 128 ? 128 : len;
    fprintf(stderr, "[DBG] %s canon len=%zu hex:", tag, len);
    for (size_t i = 0; i < n; i++)
        fprintf(stderr, "%02x", buf[i]);
    if (len > n)
        fprintf(stderr, "...");
    fputc('\n', stderr);
}
//---------------------------------

int main(int argc, char **argv)
{
    if (argc != 2)
    {
        fprintf(stderr, "Usage: %s /var/log/audit/audit.log.1\n", argv[0]);
        return 1;
    }
    const char *log_path = argv[1];

    OpenSSL_add_all_algorithms();
    ERR_load_crypto_strings();
    if (gm_crypto_init() != 0)
        diee("gm init failed");

    /* 硬性要求：必须有 sm3 */
    if (!EVP_get_digestbyname("sm3"))
        diee("[ERROR] OpenSSL 无 sm3，无法国密封印");

    /* 读取 salt 并派生 P12 口令（hex） */
    unsigned char salt[SALT_BYTES];
    if (read_salt_hexfile(SALT_PATH, salt, SALT_BYTES) != 0)
        diee("read salt failed");
    char *secret = getpass("Enter master passphrase (for P12): ");
    if (!secret || !*secret)
        diee("empty passphrase");
    unsigned char derived[DERIVE_BYTES];
    if (derive_pass(secret, salt, sizeof(salt), DERIVE_ITERATIONS, derived, sizeof(derived)) != 0)
        diee("PBKDF2(SM3) failed");
    char passhex[DERIVE_BYTES * 2 + 1];
    hexdump(derived, sizeof(derived), passhex, sizeof(passhex));
    OPENSSL_cleanse(secret, strlen(secret));

    /* 载入 SM2 私钥与证书 */
    EVP_PKEY *pkey = NULL;
    X509 *cert = NULL;
    if (load_pkey_cert_from_p12(P12_PATH, passhex, &pkey, &cert) != 0)
        diee("load P12 failed (passphrase wrong?)");

    /* 基本校验：必须是 SM2 曲线 */
    EC_KEY *eckey = EVP_PKEY_get0_EC_KEY(pkey);
    if (!eckey)
        diee("[ERROR] P12 不是 EC/SM2 私钥");
    const EC_GROUP *grp = EC_KEY_get0_group(eckey);
    if (!grp || EC_GROUP_get_curve_name(grp) != NID_sm2)
        diee("[ERROR] 私钥不是 SM2 曲线");

    /* 计算链式 SM3 */
    unsigned char chain[32];
    uint64_t lines = 0;
    if (sm3_chain_file(log_path, chain, &lines) != 0)
        diee("compute chain failed");
    char chain_hex[65];
    hexdump(chain, 32, chain_hex, sizeof(chain_hex));

    /* 证书指纹（SM3） */
    char fpr_hex[65];
    if (x509_sm3_fingerprint_hex(cert, fpr_hex, sizeof(fpr_hex)) != 0)
        diee("fingerprint failed");

    // === 生成行级索引，并将其 SM3 纳入 seal ===
    char *idx_path = NULL;
    char idx_sm3_hex[65];
    uint64_t lines_by_idx = 0;
    if(build_line_index(log_path,&idx_path,idx_sm3_hex,&lines_by_idx)!=0)
        diee("build insex failed");
    if(lines_by_idx!=lines)
    {
        fprintf(stderr,"[WARN] index lines(%llu) != chain lines(%llu)\n",
                (unsigned long long)lines_by_idx,(unsigned long long)lines);
    }

    /* 规范串 */
    char *canon = build_canonical(log_path, chain_hex, lines, fpr_hex,idx_sm3_hex);
    if (!canon)
        diee("asprintf failed");

    //-----------------------------------
    dump_hex_prefix("seal", (const unsigned char *)canon, strlen(canon));
    //-----------------------------------

    /* 用 SM2+SM3 做签名（强制 SM2/SM3） */
    unsigned char *sig = NULL;
    size_t siglen = 0;

    fprintf(stderr, "[DBG] seal canon len=%zu\n", strlen(canon));
    if (gm_sm2_sign(pkey, (const unsigned char*)canon, strlen(canon),
                SM2_ID, &sig, &siglen) != 0)
        diee("SM2 sign failed");
    //----------------------------------------------

    /* 自验签（同一进程、同一 canon）——如果这里都失败，说明签名流程/控件仍有问题 */
    {
        EVP_PKEY *pub_self = X509_get_pubkey(cert);
        if (!pub_self)
            diee("X509_get_pubkey(self) failed");

        int vself = gm_sm2_verify(pub_self,
                                  (const unsigned char *)canon, strlen(canon),
                                  SM2_ID, sig, siglen);
        EVP_PKEY_free(pub_self);
        if (vself != 1)
        {
            diee("[INTERNAL] self-verify failed (sign/verify parameters mismatch)");
        }
    }

    //----------------------------------------------
    char *sig_b64 = b64_encode(sig, siglen);
    OPENSSL_free(sig);

    /* 证书 DER -> base64 */
    unsigned char *der = NULL;
    int derlen = i2d_X509(cert, &der);
    if (derlen <= 0)
        diee("i2d_X509 failed");
    char *cert_b64 = b64_encode(der, derlen);
    OPENSSL_free(der);

    /* 写 seal 文件 */
    char *seal_path = NULL;
    if (asprintf(&seal_path, "%s.seal", log_path) < 0)
        diee("asprintf seal");
    FILE *sf = fopen(seal_path, "w");
    if (!sf)
        die("fopen seal");
    fprintf(sf,
            "# audit-seal v1 (SM2/SM3)\n"
            "version=1\n"
            "hash=sm3\n"
            "sign=sm2\n"
            "sm2_id=%s\n"
            "path=%s\n"
            "records=%llu\n"
            "chain=%s\n"
            "key_fpr=%s\n"
            "index_sm3=%s\n"
            "cert_der_base64=%s\n"
            "sig_base64=%s\n",
            SM2_ID, log_path, (unsigned long long)lines, chain_hex, fpr_hex,
            idx_sm3_hex,cert_b64, sig_b64);
    fclose(sf);
    chmod(seal_path, 0600);

    fprintf(stderr, "[OK] sealed %s -> %s\n", log_path, seal_path);

    free(seal_path);
    free(sig_b64);
    free(cert_b64);
    free(canon);
    X509_free(cert);
    EVP_PKEY_free(pkey);
    gm_crypto_cleanup();
    return 0;
}



